package com.usefullc.crawler.selenium;

import com.usefullc.crawler.constants.CrawlerConstants;
import com.usefullc.crawler.enums.WebDriverTypeEnum;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author: Sampson
 * @date: 2022-03-29 16:56
 */
public class CrawlerWebDriverBuilder {

    private static Logger logger = LoggerFactory.getLogger(CrawlerWebDriverBuilder.class);

    private static int maxWebDriverCount = 4;

    public static void setMaxWebDriverCount(int count){
        maxWebDriverCount = count;
    }

    private static BlockingQueue<CrawlerWebDriver> webDriverBlockingQueue = new ArrayBlockingQueue<>(maxWebDriverCount);
    private static BlockingQueue<CrawlerWebDriver> webDriverBlockingWorkQueue = new ArrayBlockingQueue<>(maxWebDriverCount);
    private static AtomicInteger newCount = new AtomicInteger();

    private static List<String> proxyList = new ArrayList<>();
    static {
        try{
            List<String> strings = FileUtils.readLines(new File("/Users/long3/getip.txt"), "utf-8");
            for (String string : strings) {
                proxyList.add(string);
            }
        }catch (Exception e){
            logger.error("error",e);

        }
//        proxyList.add("localhost:8888");
//        proxyList.add("localhost:8889");
    }

    public static CrawlerWebDriver buildFirefox(Map<String,String> paramMap) {
        System.setProperty("webdriver.firefox.logfile", "/dev/null");
        FirefoxProfile profile = new FirefoxProfile();
//        ProfilesIni allProfiles = new ProfilesIni();
//        FirefoxProfile profile = allProfiles.getProfile("NoJs");

//        profile.setPreference("javascript.enabled",false);
        FirefoxOptions options = new FirefoxOptions();
        options.setProfile(profile);
        options.addArguments("--start-maximized");
//        options.addArguments("--disable-javascript");
        options.addArguments("--disable-infobars");
        options.addArguments("--disable-logging");
//        options.addArguments("headless");
        if(!BooleanUtils.toBoolean(paramMap.get(CrawlerConstants.CRAWL_ENABLE_IMAGE))){
            options.addPreference("permissions.default.image", 2);
        }
        if(!BooleanUtils.toBoolean(paramMap.get(CrawlerConstants.CRAWL_ENABLE_JAVASCRIPT))){
            //TODO
        }
//        chromePrefs.put("profile.managed_default_content_settings.css",2);
        if(!BooleanUtils.toBoolean(paramMap.get(CrawlerConstants.CRAWL_ENABLE_STYLE))){
            options.addPreference("permissions.default.stylesheet", 2);
        }
//        options.addPreference("dom.ipc.plugins.enabled.libflashplayer.so",false);
        options.addPreference("excludeSwitches", "enable-automation");
//            options.addPreference("excludeSwitches","--disable-logging");
//            options.addPreference("excludeSwitches"," enable-logging");
//            options.setExperimentalOption("excludeSwitches", Collections.singletonList("enable-automation"));
        FirefoxDriver driver = new FirefoxDriver(options);
        driver.manage().window().maximize();
//            driver.setLogLevel(Level.OFF);
        CrawlerWebDriver webDriver = new CrawlerWebDriver();
        webDriver.setWebDriver(driver);
        webDriver.setUse(false);
        return webDriver;
    }

    public static CrawlerWebDriver buildChrome(Map<String,String> paramMap) {
        ChromeOptions options = new ChromeOptions();
        //去掉chrome 正受到自动测试软件的控制
//            options.addArguments("disable-infobars");

        options.addArguments("--disable-infobars");
        options.addArguments("--disable-extensions");
        // 允许重定向
        options.addArguments("--disable-web-security");
        // 最大化
        options.addArguments("--start-maximized");
        options.addArguments("--no-sandbox");

        //
//        options.addArguments("--headless");
        options.addArguments("--disable-gpu");
        options.addArguments("--no-sandbox");
        options.addArguments("--allow-RUNNING-insecure-content'");  //设置proxy时添加的额外参数，允许不安全的证书
        options.addArguments("--ignore-certificate-errors");  //设置proxy时添加的额外参数，允许不安全的证书
        options.addArguments("user-agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36'");

//            options.addArguments("--proxy-server=%s");
//            options.addArguments("--enable-automation");

//            options.setExperimentalOption("debuggerAddress","127.0.0.1:9222");

        //开启开发者模式
        options.setExperimentalOption("excludeSwitches", Collections.singletonList("enable-automation"));

        HashMap<String, Object> chromePrefs = new HashMap<>();
//        chromePrefs.put("permissions.default.stylesheet",2);
        if(!BooleanUtils.toBoolean(paramMap.get(CrawlerConstants.CRAWL_ENABLE_IMAGE))){
            chromePrefs.put("profile.managed_default_content_settings.images", 2);
        }
        if(!BooleanUtils.toBoolean(paramMap.get(CrawlerConstants.CRAWL_ENABLE_JAVASCRIPT))){
            chromePrefs.put("profile.managed_default_content_settings.javascript", 2);
        }
//        chromePrefs.put("profile.managed_default_content_settings.css",2);
        if(!BooleanUtils.toBoolean(paramMap.get(CrawlerConstants.CRAWL_ENABLE_STYLE))){
//            chromePrefs.put("profile.managed_default_content_settings.stylesheet", 2);
            //TODO
        }
//        chromePrefs.put("profile.managed_default_content_settings.permissions.default.stylesheet",2);
//        chromePrefs.put("profile.managed_default_content_settings.javascript",2);
        options.setExperimentalOption("prefs", chromePrefs);

//        WebDriver driver = new ChromeDriver(options);
//        driver.manage().window().maximize();

        DesiredCapabilities capabilities = DesiredCapabilities.chrome();

        //proxy 处理
        if(BooleanUtils.toBoolean(paramMap.get(CrawlerConstants.CRAWL_ENABLE_PROXY))){
            Proxy proxy = new Proxy();
            int index = newCount.get() % proxyList.size();
            String proxyPort = proxyList.get(index);
            proxy.setHttpProxy(proxyPort);
            proxy.setSslProxy(proxyPort);
            capabilities.setCapability("proxy", proxy);
        }

//        ChromeOptions options = new ChromeOptions();
        options.addArguments("start-maximized");

        capabilities.setCapability(ChromeOptions.CAPABILITY, options);

        WebDriver driver = new ChromeDriver(capabilities);


        CrawlerWebDriver webDriver = new CrawlerWebDriver();
        webDriver.setNo(newCount.get());
        String runRdMs = paramMap.get(CrawlerConstants.CRAWL_RUN_RD_MS);
        String[] rds = runRdMs.split("-");
        webDriver.setRunMinMs(Integer.valueOf(rds[0]));
        webDriver.setRunMaxMs(Integer.valueOf(rds[1]));
        webDriver.setWebDriver(driver);
        webDriver.setUse(false);
        return webDriver;
    }

    private static void preExecute(WebDriver driver) {
        JavascriptExecutor driverJs = ((JavascriptExecutor) driver);
        driverJs.executeScript("Object.defineProperty(navigator, 'webdriver', {\n" +
                "      get: () => undefined\n" +
                "    })");
    }

    private static void init(String type, int num, Map<String,String> paramMap) {
        for (int i = 0; i < num; i++) {
            CrawlerWebDriver webDriver;
            if (StringUtils.equalsIgnoreCase(type, WebDriverTypeEnum.FIREFOX.getCode())) {
                webDriver = buildFirefox(paramMap);
            } else {
                webDriver = buildChrome(paramMap);
//                webDriverBlockingQueue.add(webDriver);
            }
            webDriverBlockingQueue.add(webDriver);
            newCount.incrementAndGet();
//            webDriverBlockingFreeQueue.add(webDriver);
        }
    }


    public static Object executeScript(WebDriver driver, String script) {
        try{
            JavascriptExecutor driverJs = ((JavascriptExecutor) driver);
            return driverJs.executeScript(script);
        }catch (Exception e){
            logger.error("execute script failed! script=\n{}",script);
            throw e;
        }
    }

    private static synchronized CrawlerWebDriver get(String type, String url, Map<String,String> paramMap){
        try {
//            synchronized (webDriverBlockingQueue) {
            if (!webDriverBlockingWorkQueue.isEmpty()) {
                CrawlerWebDriver[] crawlerWebDrivers = webDriverBlockingWorkQueue.toArray(new CrawlerWebDriver[]{});
                for (CrawlerWebDriver crawlerWebDriver : crawlerWebDrivers) {
                    //客户端已死掉的
                    boolean dead = false;
                    WebDriver webDriver = crawlerWebDriver.getWebDriver();
                    try {
                        webDriver.getCurrentUrl();
                    } catch (Exception e) {
                        webDriverBlockingWorkQueue.remove(crawlerWebDriver);
                        dead = true;
                    }
                    //客户端僵尸，没有及时回收到 webDriverBlockingQueue
                    if (!dead && crawlerWebDriver.isUse()) {
                        int timeout = 5 * 60 * 1000;
                        if (System.currentTimeMillis() - crawlerWebDriver.getUseTime() > timeout) {
                            boolean state = webDriverBlockingWorkQueue.remove(crawlerWebDriver);
                            if (state) {
                                webDriverBlockingQueue.add(crawlerWebDriver);
                            }
                        }
                    }
                }
            }else if(!webDriverBlockingWorkQueue.isEmpty()){
                CrawlerWebDriver[] crawlerWebDrivers = webDriverBlockingWorkQueue.toArray(new CrawlerWebDriver[]{});
                for (CrawlerWebDriver crawlerWebDriver : crawlerWebDrivers) {
                    WebDriver webDriver = crawlerWebDriver.getWebDriver();
                    try {
                        webDriver.getCurrentUrl();
                    } catch (Exception e) {
                        webDriverBlockingWorkQueue.remove(crawlerWebDriver);
                    }
                }
            }
            int newCount = maxWebDriverCount - (webDriverBlockingQueue.size() + webDriverBlockingWorkQueue.size());
            if (newCount > 0) {
                init(type, newCount,paramMap);
            }


            CrawlerWebDriver crawlerWebDriver = webDriverBlockingQueue.take();
//            aliveCount.incrementAndGet();
            WebDriver webDriver = crawlerWebDriver.getWebDriver();
            while (true) {
                try {
                    webDriver.getCurrentUrl();
                    break;
                } catch (Exception e) {
//                    webDriverBlockingQueue.remove(crawlerWebDriver);
                }
                crawlerWebDriver = webDriverBlockingQueue.take();
//                aliveCount.incrementAndGet();
                webDriver = crawlerWebDriver.getWebDriver();
            }
            webDriverBlockingWorkQueue.add(crawlerWebDriver);
            return  crawlerWebDriver;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }


    public static  CrawlerWebDriver open(String type, String url, Map<String,String> paramMap) {
        try {
            CrawlerWebDriver crawlerWebDriver = get(type, url, paramMap);
            WebDriver webDriver = crawlerWebDriver.getWebDriver();

            //处理打开url 频率
            long takeTime = System.currentTimeMillis() - crawlerWebDriver.getUseTime();
            if(takeTime < crawlerWebDriver.getRunMaxMs()){
                long runMinMs = crawlerWebDriver.getRunMinMs();
                long min = NumberUtils.min(runMinMs,takeTime);
                long max = NumberUtils.max(runMinMs,takeTime);
                long rdNum = RandomUtils.nextLong(min, max);
                logger.info("sleep {} ms at {} ",rdNum,crawlerWebDriver.getNo());
                Thread.sleep(rdNum);
            }
            //end

            if (crawlerWebDriver.isUse()) {
                crawlerWebDriver.setUseTime(System.currentTimeMillis());
                webDriver.navigate().to(url);
            } else {
                crawlerWebDriver.setUse(true);
                crawlerWebDriver.setUseTime(System.currentTimeMillis());
                webDriver.get(url);
                if (StringUtils.equalsIgnoreCase(type, WebDriverTypeEnum.CHROME.getCode())) {
                    preExecute(crawlerWebDriver.getWebDriver());
                }
            }
            return crawlerWebDriver;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static void scrollDown(WebDriver driver) {
        JavascriptExecutor driverJs = ((JavascriptExecutor) driver);
        try {
            for (int i = 0; i < 50; i++) {
                driverJs.executeScript("scrollTo(0," + (i * 100) + ")");
                Thread.sleep(10);
            }
        } catch (Exception e) {
        }
    }

    public static void close(CrawlerWebDriver crawlerWebDriver) {
        if (crawlerWebDriver != null) {
            synchronized (crawlerWebDriver){
                boolean state = webDriverBlockingWorkQueue.remove(crawlerWebDriver);
                if (state) {
                    int newCount = maxWebDriverCount - (webDriverBlockingQueue.size() + webDriverBlockingWorkQueue.size());
                    if(newCount >= 0){
                        webDriverBlockingQueue.add(crawlerWebDriver);
                    }
                }
            }
//            aliveCount.decrementAndGet();
        }
    }

    public static void destory() {
        CrawlerWebDriver crawlerWebDriver = webDriverBlockingQueue.poll();
        while (crawlerWebDriver != null) {
            crawlerWebDriver.getWebDriver().quit();

        }
    }
}
